#!/bin/env python
# -*- encoding: utf8 -*-
"""
cmcm cmdb external inventory script
=================================

"""

# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible,
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

######################################################################

import sys
from argparse import ArgumentParser
import ConfigParser
import os
import re
from time import time
from mongorest import mongorest

import simplejson as json
from ansible import errors
from itertools import groupby

class CmdbInventory(object):

    def __init__(self):
        """ Main execution path """

        self.cache = dict()
        self.inventory = dict()  # A list of groups and the hosts in that group

        # Read settings and parse CLI arguments
        self.read_settings()
        self.parse_cli_args()

        # Cache
        if self.args.refresh_cache:
            self.update_cache()
        elif not self.is_cache_valid():
            self.update_cache()
        else:
            self.load_inventory_from_cache()

        data_to_print = ""

        # Data to print
        if self.args.host:
            data_to_print = self.get_host_info()

        elif self.args.list:
            # Display list of instances for inventory
            data_to_print = self.json_format_dict(self.inventory, True)

        else:  # default action with no options
            data_to_print = self.json_format_dict(self.inventory, True)

        print data_to_print

    def get_host_list(self):
        '''根据json从mongodb中选出机器'''
        handler = mongorest(self.mongodbapi,self.mongodb)
        result = handler.find(self.mongo_talbe,self.hosts)
        if result:
            for host in result:
                if host['ip_address']:
                    _host = host['ip_address']
                # all分组
                self.push(self.inventory, 'all', _host)

            # 按照指定的字段做分组
            for group in self.groups:
                hosts = list()
                for host in result:
                    hosts.append((host[group],host['ip_address']))
                for key, _value in groupby(hosts, lambda x: x[0]):
                    for value in _value:
                        self.push(self.inventory,key,value[1])
        else:
            print 'WARN: get mongodb api failed !!'
            exit(1)

    def get_host_attr(self):
        '''
        获取host里面的属性作为hostvar
        '''
        handler = mongorest(self.mongodbapi,self.mongodb)
        result = handler.find(self.mongo_talbe,self.hosts)
        if result:
            #获取所有hsot的属性作为hostvar
            for host in result:
                self.cache[host['ip_address']] = dict()
                for k,v in host.iteritems():
                    self.push(self.cache[host['ip_address']],k,v)

    def get_host_info(self):
        """ Get variables about a specific host """

        if not self.cache or len(self.cache) == 0:
            # Need to load index from cache
            self.load_cache_from_cache()

        if not self.args.host in self.cache:
            # try updating the cache
            self.update_cache()

            if not self.args.host in self.cache:
                # host might not exist anymore
                return self.json_format_dict({}, True)

        return self.json_format_dict(self.cache[self.args.host], True)

    def is_cache_valid(self):
        """ Determines if the cache files have expired, or if it is still valid """

        if os.path.isfile(self.cache_path_inventory):
            mod_time = os.path.getmtime(self.cache_path_inventory)
            current_time = time()
            if (mod_time + self.cache_max_age) > current_time:
                if os.path.isfile(self.cache_path_cache):
                    return True

        return False

    def read_settings(self):
        """ Reads the settings from the xbox.ini file """

        config = ConfigParser.SafeConfigParser()

        # ---- 读取全局配置
        config.read(os.path.dirname(os.path.realpath(__file__)) + '/cmdb.ini')
        # 获取mongodbapi
        self.mongodbapi = config.get('cmdb', 'mongodbapi')
        # 获取数据库
        self.mongodb = config.get('cmdb', 'mongodb')
        # 获取数据库
        self.mongo_talbe = config.get('cmdb', 'mongo_talbe')
        # 用户cache目录所在路径
        user_cache_path = os.path.expanduser(config.get('cmdb', 'user_cache_path'))
        # 用户可设置的cache时间间隔,如果超出范围将以此处的最大值代替
        [ cache_age_min,cache_age_max ] = config.get('cmdb', 'valid_cache_age').split(',')
        # ansible获取机器列表时依据的find json串
        _hosts = config.items('filter')
        self.hosts = dict()
        for i in _hosts:
            self.hosts[i[0]] = i[1]
        # ansible分组的依据字段
        _groups = config.get('groups','groups')
        self.groups = _groups.split(',')
        # cache超时时间(单位:秒),用户可配置,但超出规定的范围将以全局提供的边界值代替
        self.cache_max_age = config.getint('cmdb', 'cache_max_age')

        # ---- 修正用户配置
        # cache 失效时间不合理?
        if self.cache_max_age < int(cache_age_min):
            self.cache_max_age = cache_age_min
        elif self.cache_max_age > int(cache_age_max):
            self.cache_max_age = cache_age_max
        # cache相关路径
        self.cache_path_cache = user_cache_path + "/.ansible-xbox.cache"
        self.cache_path_inventory = user_cache_path + "/.ansible-xbox.index"

    def parse_cli_args(self):
        """ Command line argument processing """

        parser = ArgumentParser(description='Produce an Ansible Inventory file based on Xbox')
        parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
        parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
        parser.add_argument('--refresh-cache', action='store_true', default=False,
                            help='Force refresh of cache by making API requests to xbox (default: False - use cache files)')
        self.args = parser.parse_args()

    def update_cache(self):
        """ Make calls to xbox and save the output in a cache """

        self.get_host_list()
        self.get_host_attr()

        self.write_to_cache(self.cache, self.cache_path_cache)
        self.write_to_cache(self.inventory, self.cache_path_inventory)

    def push(self, my_dict, key, element):
        """ Pushed an element onto an array that may not have been defined in the dict """

        if key not in my_dict:
            # new tag
            my_dict[key] = [element]
        elif element not in my_dict[key]:
            # tag with new value
            my_dict[key].append(element)
        else:
            # already have this tag and value
		    pass

    def load_cache_from_cache(self):
        """ Reads the cache from the cache file sets self.cache """

        cache = open(self.cache_path_cache, 'r')
        json_cache = cache.read()
        self.cache = json.loads(json_cache)

    def load_inventory_from_cache(self):
        """ Reads the index from the cache file sets self.index """

        cache = open(self.cache_path_inventory, 'r')
        json_inventory = cache.read()
        self.inventory = json.loads(json_inventory)

    def write_to_cache(self, data, filename):
        """ Writes data in JSON format to a file """

        json_data = self.json_format_dict(data, True)
        cache = open(filename, 'w')
        cache.write(json_data)
        cache.close()

    def to_safe(self, word):
        """ Converts 'bad' characters in a string to underscores so they can be used as Ansible groups """

        return re.sub("[^A-Za-z0-9\-]", "_", word)

    def json_format_dict(self, data, pretty=False):
        """ Converts a dict to a JSON object and dumps it as a formatted string """

        if pretty:
            return json.dumps(data, sort_keys=True, indent=2)
        else:
            return json.dumps(data)


class AnsibleXboxResponseError(errors.AnsibleError):
    pass

class AnsibleXboxQueryError(errors.AnsibleError):
    pass
CmdbInventory()